package com.jzo2o.market.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.model.PageResult;
import com.jzo2o.common.utils.*;
import com.jzo2o.market.constants.TabTypeConstants;
import com.jzo2o.market.enums.CouponConfigDrawTypeEnum;
import com.jzo2o.market.mapper.CouponMapper;
import com.jzo2o.market.model.domain.Coupon;
import com.jzo2o.market.model.dto.request.CouponQueryForPageReqDTO;
import com.jzo2o.market.model.dto.request.CouponSaveReqDTO;
import com.jzo2o.market.model.dto.response.CouponInfoResDTO;
import com.jzo2o.market.model.dto.response.SeizeCouponInfoResDTO;
import com.jzo2o.market.service.ICouponService;
import com.jzo2o.market.service.ICouponUserService;
import com.jzo2o.market.service.ICouponWriteOffService;
import com.jzo2o.market.utils.CouponUtils;
import com.jzo2o.mysql.utils.PageUtils;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static com.jzo2o.market.constants.RedisConstants.RedisKey.*;
import static com.jzo2o.market.enums.CouponConfigStatusEnum.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author itcast
 * @since 2023-09-16
 */
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {
    private static final int MILLION = 1000000;

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private ICouponUserService couponUserService;

    @Resource
    private ICouponWriteOffService couponWriteOffService;

    @Override
    public PageResult<CouponInfoResDTO> queryForPage(CouponQueryForPageReqDTO couponQueryForPageReqDTO) {
        LocalDateTime now = DateUtils.now();
        // 1.查询准备
        LambdaQueryWrapper<Coupon> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 查询条件
        lambdaQueryWrapper
                .like(StringUtils.isNotEmpty(couponQueryForPageReqDTO.getName()), Coupon::getName, couponQueryForPageReqDTO.getName())
                .eq(ObjectUtils.isNotNull(couponQueryForPageReqDTO.getDrawType()), Coupon::getDrawType, couponQueryForPageReqDTO.getDrawType());

        // 通过该段逻辑减少了状态修改任务执行频率，时效性更高
        // 待生效 状态未开始，且未到达发放时间的优惠券均认为未开始（部分优惠券状态未及改成进行中排除在外）
        if (NO_DISTRIBUTE.equals(couponQueryForPageReqDTO.getStatus())) {
            lambdaQueryWrapper.eq(Coupon::getStatus, couponQueryForPageReqDTO.getStatus())
                    .ge(Coupon::getDistributeStartTime, now);
        }
        // 进行中，1.状态在待生效（未及时修改状态）但发放时间已经到，2.状态已经修改为进行中，且当前时间在发放时间范围内的
        if (DISTRIBUTING.equals(couponQueryForPageReqDTO.getStatus())) {
            lambdaQueryWrapper.in(Coupon::getStatus, NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus())
                    .le(Coupon::getDistributeStartTime, now)
                    .ge(Coupon::getDistributeEndTime, now);
        }
        // 已失效  过了发放时间或撤销了优惠券
        if (LOSE_EFFICACY.equals(couponQueryForPageReqDTO.getStatus())) {
            lambdaQueryWrapper.eq(Coupon::getStatus, LOSE_EFFICACY.getStatus());
        }

        // 排序
        lambdaQueryWrapper.orderByDesc(Coupon::getCreateTime);

        // 分页
        Page<Coupon> couponPage = new Page<>(couponQueryForPageReqDTO.getPageNo().intValue(), couponQueryForPageReqDTO.getPageSize().intValue());
        couponPage = baseMapper.selectPage(couponPage, lambdaQueryWrapper);

        if (CollUtil.isEmpty(couponPage.getRecords())) {
            return PageUtils.toPage(couponPage, CouponInfoResDTO.class);
        }

        return PageUtils.toPage(couponPage, CouponInfoResDTO.class, (originObject, targetObject) -> {
            targetObject.setStatus(getStatus(originObject.getDistributeStartTime(), originObject.getDistributeEndTime(), originObject.getStatus()));
        });
    }

    @Override
    public CouponInfoResDTO queryById(Long id) {
        // 1.获取活动
        Coupon coupon = baseMapper.selectById(id);
        // 判空
        if (coupon == null) {
            return new CouponInfoResDTO();
        }
        // 2.数据转换，并返回信息
        CouponInfoResDTO couponInfoResDTO = BeanUtils.toBean(coupon, CouponInfoResDTO.class);
        // 设置状态
        couponInfoResDTO.setStatus(getStatus(coupon.getDistributeStartTime(), coupon.getDistributeEndTime(), coupon.getStatus()));
        // 3.领取数量
        Integer receiveNum = couponUserService.countReceiveNumByCouponId(coupon.getId());
        couponInfoResDTO.setReceiveNum(NumberUtils.null2Zero(receiveNum));
        // 4.核销量
        Integer writeOffNum = couponWriteOffService.countByCouponId(id);
        couponInfoResDTO.setWriteOffNum(NumberUtils.null2Zero(writeOffNum));

        return couponInfoResDTO;
    }

    @Override
    public void save(CouponSaveReqDTO couponSaveReqDTO) {
        // 1.逻辑校验
        couponSaveReqDTO.check();
        // 2.活动数据组装
        // 转换
        Coupon coupon = BeanUtils.toBean(couponSaveReqDTO, Coupon.class);
        // 设置默认状态
        coupon.setStatus(NO_DISTRIBUTE.getStatus());

        //id为空，说明为新增动作，需构建id及判断领取类型
        if (couponSaveReqDTO.getId() == null) {
            handleNewCoupon(coupon, couponSaveReqDTO.getDrawType(), couponSaveReqDTO.getCreateBy(), couponSaveReqDTO.getCreator());
        } else {
            //更新动作设置更新人姓名
            coupon.setUpdateBy(couponSaveReqDTO.getUpdateBy());
            coupon.setUpdator(couponSaveReqDTO.getUpdator());
        }

        //排序字段
//        long sortBy = DateUtils.toEpochMilli(activity.getDistributeStartTime()) * MILLION + activity.getId() % MILLION;
        // 3.保存
        saveOrUpdate(coupon);
    }

    /**
     * 处理新优惠券的生成与设置。
     * 为新优惠券设置唯一ID，并根据抽奖类型生成兑换码。
     *
     * @param coupon   新生成的优惠券对象，需要设置ID和兑换码。
     * @param drawType 抽奖类型，用于判断是否需要生成兑换码。
     */
    private void handleNewCoupon(Coupon coupon, Integer drawType, Long createBy, String creator) {
        // 为优惠券生成唯一的ID
        coupon.setId(IdUtils.getSnowflakeNextId());
        coupon.setCreateBy(createBy);
        coupon.setCreator(creator);

        // 判断领券类型，如果是兑换码类型，则生成兑换码
        if (drawType.equals(CouponConfigDrawTypeEnum.REDEEM_CODE.getType())) {
            generateAndSetRedeemCode(coupon);
        }
    }

    /**
     * 生成并设置优惠券的兑换码。
     * 该方法尝试生成一个唯一的兑换码，并将其设置到优惠券对象中。如果生成的兑换码是唯一的，
     * 则设置成功；如果生成的兑换码已存在，则重试生成，直到成功设置一个唯一的兑换码或达到最大重试次数。
     *
     * @param coupon 优惠券对象，将要设置兑换码的对象。
     * @throws RuntimeException 如果经过多次尝试都无法设置一个唯一的兑换码，则抛出运行时异常。
     */
    private void generateAndSetRedeemCode(Coupon coupon) {
        String redeemCode;
        boolean isRedeemCodeSet = false;
        int retryCount = 0;
        final int maxRetryCount = 3; // 根据实际情况调整重试次数
        while (!isRedeemCodeSet && retryCount < maxRetryCount) {
            redeemCode = CouponUtils.generateRedeemCode();
            if (!checkDuplicatedRedeemCode(redeemCode)) {
                coupon.setRedeemCode(redeemCode);
                isRedeemCodeSet = true;
            }
            retryCount++;
        }
        if (!isRedeemCodeSet) {
            // 处理无法设置兑换码的情况，例如记录日志、抛出异常等
            throw new CommonException("生成唯一兑换码失败");
        }
    }


    /**
     * 检查兑换码是否重复。
     * 通过Lambda查询方式统计数据库中具有相同兑换码的优惠券数量，如果数量大于0，则表示该兑换码已重复使用。
     *
     * @param redeemCode 待检查的兑换码
     * @return 如果兑换码存在重复，则返回true；否则返回false。
     */
    private boolean checkDuplicatedRedeemCode(String redeemCode) {
        // 使用Lambda表达式查询数据库中与指定兑换码相同的记录数量
        Integer count = lambdaQuery().eq(Coupon::getRedeemCode, redeemCode).count().intValue();
        // 判断记录数量是否大于0，以确定兑换码是否重复
        return count > 0;
    }


    @Override
    public List<SeizeCouponInfoResDTO> queryForListFromCache(Integer tabType) {

        // 查询数据
        Object seizeCouponInfoStr = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(seizeCouponInfoStr)) {
            return CollUtils.emptyList();
        }
        // 查询id列表
        List<SeizeCouponInfoResDTO> data = JsonUtils.toList(seizeCouponInfoStr.toString(), SeizeCouponInfoResDTO.class);
        if (tabType.equals(TabTypeConstants.SEIZING)) {
            // 抢券中
            // 过滤掉已经结束的优惠券活动
            HashOperations hashOperations = redisTemplate.opsForHash();
            return data.stream()
                    .filter(seizeCouponInfoResDTO -> DISTRIBUTING.getStatus() == getStatus(seizeCouponInfoResDTO.getDistributeStartTime(), seizeCouponInfoResDTO.getDistributeEndTime(), seizeCouponInfoResDTO.getStatus()))
                    .peek(seizeCouponInfoResDTO -> {
                        // 活动状态
                        seizeCouponInfoResDTO.setStatus(DISTRIBUTING.getStatus());
                        // 剩余数量
                        String redisKey = String.format(COUPON_RESOURCE_STOCK, seizeCouponInfoResDTO.getId() % 10);
                        Object stockObj = hashOperations.get(redisKey, seizeCouponInfoResDTO.getId());
                        Integer stock = ObjectUtils.parse(stockObj, Integer.class);
                        seizeCouponInfoResDTO.setRemainNum(NumberUtils.null2Zero(stock));
                    }).collect(Collectors.toList());
        } else {
            // 即将奖券
            // 过滤掉已经开始的优惠券活动
            return data.stream()
                    .filter(seizeCouponInfoResDTO -> NO_DISTRIBUTE.getStatus() == getStatus(seizeCouponInfoResDTO.getDistributeStartTime(), seizeCouponInfoResDTO.getDistributeEndTime(), seizeCouponInfoResDTO.getStatus()))
                    .peek(seizeCouponInfoResDTO -> {
                        seizeCouponInfoResDTO.setStatus(NO_DISTRIBUTE.getStatus());
                    }).collect(Collectors.toList());
        }
    }

    @Override
    public CouponInfoResDTO getCouponInfoByIdFromCache(Long id) {
        // 1.从缓存中获取活动信息
        Object couponList = redisTemplate.opsForValue().get(ACTIVITY_CACHE_LIST);
        if (ObjectUtils.isNull(couponList)) {
            return null;
        }
        // 2.过滤指定活动信息
        List<CouponInfoResDTO> list = JsonUtils.toList(couponList.toString(), CouponInfoResDTO.class);
        if (CollUtils.isEmpty(list)) {
            return null;
        }
        // 3.过滤指定活动
        return list.stream()
                .filter(couponInfoResDTO -> couponInfoResDTO.getId().equals(id))
                .findFirst().orElse(null);
    }

    @Override
    public void preHeat() {
        LocalDateTime now = DateUtils.now();

        // 1.查询数据
        List<Coupon> list = lambdaQuery().ge(Coupon::getDistributeEndTime, now)
                .ge(Coupon::getDistributeStartTime, now.minusDays(30))
                .in(Coupon::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .orderByAsc(Coupon::getDistributeStartTime)
                .list();
        if (CollUtils.isEmpty(list)) {
            //防止缓存穿透
            list = new ArrayList<>();
        }
        // 2.数据转换
        List<SeizeCouponInfoResDTO> seizeCouponInfoResDTOS = BeanUtils.copyToList(list, SeizeCouponInfoResDTO.class);
        String seizeCouponInfoStr = JsonUtils.toJsonStr(seizeCouponInfoResDTOS);

        // 3.活动列表写入缓存
        redisTemplate.opsForValue().set(ACTIVITY_CACHE_LIST, seizeCouponInfoStr);

    }

    @Override
    public void activityFinished() {
        LocalDateTime now = DateUtils.now();
        // 1.查询1天内结束活动
        List<Coupon> couponList = lambdaQuery().ge(Coupon::getDistributeEndTime, now)
                .eq(Coupon::getStatus, DISTRIBUTING.getStatus())
                .gt(Coupon::getDistributeEndTime, now.minusDays(1))
                .le(Coupon::getDistributeEndTime, now)
                .list();
        if (CollUtils.isEmpty(couponList)) {
            return;
        }
        // 2.转换redisKey列表(库存和抢单列表)
        List<String> deleteRedisKeyList = new ArrayList<>();
        couponList.stream().forEach(coupon -> {
            int index = (int) (coupon.getId() % 10);
            // 资源库存redisKey
            deleteRedisKeyList.add(String.format(COUPON_RESOURCE_STOCK, index));
            // 抢券列表
            deleteRedisKeyList.add(String.format(COUPON_SEIZE_LIST, coupon.getId(), index));
        });
        // 3.删除deleteRedisKeyList
        redisTemplate.delete(deleteRedisKeyList);

    }

    @Override
    public void updateStatus() {
        LocalDateTime now = DateUtils.now();
        // 1.更新已经进行中的状态
        lambdaUpdate()
                .set(Coupon::getStatus, DISTRIBUTING.getStatus())
                .eq(Coupon::getStatus, NO_DISTRIBUTE.getStatus())
                .le(Coupon::getDistributeStartTime, now)
                .gt(Coupon::getDistributeEndTime, now)
                .update();
        // 2.更新已经结束的
        lambdaUpdate()
                .set(Coupon::getStatus, LOSE_EFFICACY.getStatus())
                .in(Coupon::getStatus, Arrays.asList(DISTRIBUTING.getStatus(), NO_DISTRIBUTE.getStatus()))
                .lt(Coupon::getDistributeEndTime, now)
                .update();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void revoke(Long id, Long updateBy, String updator) {
        // 1.活动作废
        boolean update = lambdaUpdate()
                .set(Coupon::getUpdateBy, updateBy)
                .set(Coupon::getUpdator, updator)
                .set(Coupon::getStatus, LOSE_EFFICACY.getStatus())
                .eq(Coupon::getId, id)
                .in(Coupon::getStatus, Arrays.asList(NO_DISTRIBUTE.getStatus(), DISTRIBUTING.getStatus()))
                .update();
        if (!update) {
            return;
        }
        // 2.未使用优惠券作废
        couponUserService.revoke(id);

    }

    /**
     * 获取状态，
     * 用于xxl或其他定时任务在高性能要求下无法做到实时状态
     *
     * @return
     */
    private int getStatus(LocalDateTime distributeStartTime, LocalDateTime distributeEndTime, Integer status) {
        if (NO_DISTRIBUTE.equals(status) &&
                distributeStartTime.isBefore(DateUtils.now()) &&
                distributeEndTime.isAfter(DateUtils.now())) {
            // 已经生效，还未来得及修改状态
            return DISTRIBUTING.getStatus();

        } else if (DISTRIBUTING.equals(status) &&
                distributeEndTime.isBefore(DateUtils.now())) {
            // 已经失效，还未来得及修改状态
            return LOSE_EFFICACY.getStatus();
        }
        return status;
    }

    /**
     * 根据兑换码查询优惠券
     *
     * @param redeemCode 兑换码
     * @return 优惠券
     */
    @Override
    public Coupon findByRedeemCode(String redeemCode) {
        return lambdaQuery().eq(Coupon::getRedeemCode, redeemCode).one();
    }

    /**
     * 扣减库存
     *
     * @param couponId 优惠券id
     * @param drawNum  已领取数量
     * @return 是否成功
     */
    @Override
    public boolean deductionStock(Long couponId, Integer drawNum) {
        return lambdaUpdate().eq(Coupon::getId, couponId)
                .eq(Coupon::getDrawNum, drawNum)
                .set(Coupon::getDrawNum, drawNum + 1)
                .update();
    }
}
